package mongodump

import (
	"fmt"
	"gitee.com/tym_hmm/mongo-tool/common/progress"
	"io"
	"log"
	"sync"
	"time"
)

type CusProcessListener interface {
	Start(name string, Collection string)                 //任务开始
	StartNode(nodeName string)                            //节点开始
	Pending(nodeName string, percent float64)             //节点执行
	NodeComplete(nodeName string)                         //任务节点完成
	Complete(name string, Collection string)              //任务完成
	Error(message string, name string, Collection string) //任务错误
	NodeError(nodeName string, message string)            //节点错误
}

/*重写进度########################################################*/
type CusBarWriter struct {
	sync.Mutex

	waitTime  time.Duration
	writer    io.Writer
	bars      []*CusBar
	stopChan  chan struct{}
	barLength int
	isBytes   bool

	processListener CusProcessListener
}

// NewBarWriter returns an initialized BarWriter with the given bar length and
// byte-formatting toggle, waiting the given duration between writes
func NewCusBarWriter(w io.Writer, waitTime time.Duration, barLength int, isBytes bool, processListener CusProcessListener) *CusBarWriter {
	return &CusBarWriter{
		waitTime:        waitTime,
		writer:          w,
		stopChan:        make(chan struct{}),
		barLength:       barLength,
		isBytes:         isBytes,
		processListener: processListener,
	}
}

// Attach registers the given progressor with the manager
func (manager *CusBarWriter) Attach(name string, progressor progress.Progressor) {
	if manager.processListener != nil {
		manager.processListener.StartNode(name)
	}
	pb := &CusBar{
		Name:            name,
		Watching:        progressor,
		BarLength:       manager.barLength,
		IsBytes:         manager.isBytes,
		processListener: manager.processListener,
	}
	pb.validate()

	manager.Lock()
	defer manager.Unlock()

	// make sure we are not adding the same bar again
	for _, bar := range manager.bars {
		if bar.Name == name {
			if manager.processListener != nil {
				manager.processListener.NodeError(bar.Name, fmt.Sprintf("progress bar with name '%s' already exists in manager", name))
				//panic(fmt.Sprintf("progress bar with name '%s' already exists in manager", name))
			}
			return
		}
	}
	manager.bars = append(manager.bars, pb)
}

// Detach removes the progressor with the given name from the manager. Insert
// order is maintained for consistent ordering of the printed bars.
func (manager *CusBarWriter) Detach(name string) {
	manager.Lock()
	defer manager.Unlock()
	var pb *CusBar
	for _, bar := range manager.bars {
		if bar.Name == name {
			pb = bar
			break
		}
	}
	if pb == nil {
		if manager.processListener != nil {
			manager.processListener.NodeError(name, "could not find progressor")
		} else {
			log.Println("could not find progressor")
		}
	}

	//grid := &text.GridWriter{
	//	ColumnPadding: progress.GridPadding,
	//}
	if pb.hasRendered {
		// if we've rendered this bar at least once, render it one last time
		pb.renderToListener()
	}
	//grid.FlushRows(manager.writer)

	updatedBars := make([]*CusBar, 0, len(manager.bars)-1)
	for _, bar := range manager.bars {
		// move all bars to the updated list except for the bar we want to detach
		if bar.Name != pb.Name {
			updatedBars = append(updatedBars, bar)

		}
	}
	manager.bars = updatedBars
	if manager.processListener != nil {
		manager.processListener.NodeComplete(name)
	}
}

// helper to render all bars in order
func (manager *CusBarWriter) renderAllBars() {
	manager.Lock()
	defer manager.Unlock()
	for _, bar := range manager.bars {
		bar.renderToListener()
	}
	// add padding of one row if we have more than one active bar
	if len(manager.bars) > 1 {
		// we just write an empty array here, since a write call of any
		// length to our log.Writer will trigger a new logline.
		manager.writer.Write([]byte{})
	}
}

// Start kicks of the timed batch writing of progress bars.
func (manager *CusBarWriter) Start() {
	if manager.writer == nil {
		if manager.processListener != nil {
			var itemName = ""
			if manager.bars != nil && len(manager.bars) > 0 {
				itemName = manager.bars[0].Name
			}
			manager.processListener.NodeError(itemName, "Cannot use a progress.BarWriter with an unset Writer")
		} else {
			log.Println("Cannot use a progress.BarWriter with an unset Writer")
		}
	}
	go manager.start()
}

func (manager *CusBarWriter) start() {
	if manager.waitTime <= 0 {
		manager.waitTime = DefaultWaitTime
	}
	ticker := time.NewTicker(manager.waitTime)
	defer ticker.Stop()
	for {
		select {
		case <-manager.stopChan:
			return
		case <-ticker.C:
			manager.renderAllBars()
		}
	}
}

// Stop ends the main manager goroutine, stopping the manager's bars
// from being rendered.
func (manager *CusBarWriter) Stop() {
	manager.stopChan <- struct{}{}
}

/*##############################################################################################*/
/**
重写返回 bar
*/
const (
	DefaultWaitTime = 3 * time.Second
)

type CusBar struct {
	Name            string
	BarLength       int
	IsBytes         bool
	processListener CusProcessListener
	Watching        progress.Progressor
	WaitTime        time.Duration
	stopChan        chan struct{}
	stopChanSync    chan struct{}
	hasRendered     bool
}

func (pb *CusBar) Start() {
	pb.validate()
	pb.stopChan = make(chan struct{})
	pb.stopChanSync = make(chan struct{})

}

func (pb *CusBar) validate() {
	if pb.Watching == nil {
		if pb.processListener != nil {
			pb.processListener.NodeError(pb.Name, "Cannot use a Bar with a nil Watching")
		} else {
			log.Println("Cannot use a Bar with a nil Watching")
		}
	}
	if pb.stopChan != nil {
		if pb.processListener != nil {
			pb.processListener.NodeError(pb.Name, "Cannot start a Bar more than once")
		} else {
			log.Println("Cannot start a Bar more than once")
		}
	}
}

func (pb *CusBar) Stop() {
	close(pb.stopChan)
	<-pb.stopChanSync
}

func (pb *CusBar) renderToListener() {

	pb.hasRendered = true
	currentCount, maxCount := pb.Watching.Progress()
	if maxCount == 0 {
		if pb.processListener != nil {
			pb.processListener.Pending(pb.Name, 0)
		} else {
			log.Println(pb.Name, 0)
		}
	} else {
		percent := float64(currentCount) / float64(maxCount)
		if pb.processListener != nil {
			pb.processListener.Pending(pb.Name, percent)
		} else {
			log.Println(pb.Name, percent)
		}
	}
}
